#!/bin/bash

# WARNING
# Read and confirm you understand the order of the options that should be provided to this script
# Failure to get this right, could lead to incorrect results such that you have a Firmware password set to value that you do not know
# Use of this script is at the users risk and users are expected to check the script prior to use.
# FileWave provides this script as is and holds no responsibility for misuse or unexpected results
# DO NOT USE if you do not understand the risks involved or how this script works

# Author: seanie
# Version 1.1
# Added additional verify checks for already set passwords
# Added reboot options (error, set or ignore which are set in this script).  See Script Start below for details.  Firmware requires a reboot, but using Fileset properties may be more user friendly.  Default is no reboot.

# Tested on 10.13, but expected this should work on 10.10+
# Based upon release date of firmwarepasswd, it is possible later versions of 10.9 included the firmwarepasswd command

# First option should be either: new, change or delete
# Options should be supplied as:
# new:
# new new_password

# change:
# change new_password old_password

# delete:
# delete old_password

##################
# Log Functions
# Log file
log_file="/var/log/fwcld.log"

function DoAbort {

	LogMe "$1"

	LogMe "Exiting..."
	echo "Log file: $log_file"

	exit 1
}

function LogMe {

	thedate=`date '+%Y-%m-%d %H:%M:%S'`
	echo "${thedate}:${0##*/} - $1" | tee -a $log_file
}

##################
# Variables
new_change_delete="$1"
LogMe $new_change_delete

# Get the amount of options provided
options_given=$#

##################
# Functions
# Set a new password if one is not yet set
function new_password {

	LogMe "Setting Password"

/usr/bin/expect<<EOF

spawn firmwarepasswd -setpasswd
expect {
    "Enter new password:" {
        send "$fwpassnew\r"
        exp_continue
    }
    "Re-enter new password:" {
        send "$fwpassnew\r"
        exp_continue
    }
EOF
}

# Change the current password
function change_password {

	LogMe "Changing Password"

/usr/bin/expect<<EOF

spawn firmwarepasswd -setpasswd
expect {
    "Enter password:" {
        send "$fwpassold\r"
        exp_continue
    }
    "Enter new password:" {
        send "$fwpassnew\r"
        exp_continue
    }
    "Re-enter new password:" {
        send "$fwpassnew\r"
        exp_continue
    }
}
EOF
}

# Delete the current password
function delete_password {

	LogMe echo "Deleting Password"

/usr/bin/expect<<EOF

spawn firmwarepasswd -delete
expect {
    "Enter password:" {
        send "$fwpassold\r"
        exp_continue
    }
}
EOF
}

function check_password {

	old_or_new=$1
	case $old_or_new in

		"old")
			# If old password does not match exit
			checkpass=$fwpassold
			log_message="Old password does not match"
			;;
		"new")
			# If new password already matches nothing to do
			checkpass=$fwpassnew
			log_message="Nothing to do, new password already matches"
			;;
	esac

verify_pass=$(/usr/bin/expect<<EOF

spawn firmwarepasswd -verify
expect {
    "Enter password:" {
        send "$checkpass\r"
        exp_continue
    }
}
EOF)

		# If old password does not match, abort
		if [[ $old_or_new == "old" ]] && [[ ! ${verify_pass##*:} =~ "Correct" ]]
		then
			DoAbort "$log_message"
		fi

		# If new password already matches, exit
		if [[ $old_or_new == "new" ]] && [[ ${verify_new##*:} =~ "Correct" ]]
		then
			LogMe "Nothing to do, new password already matches"
			exit 0
		fi
}

function set_password {

	firmware_passwd_update=$1

	# Get the current status of firmware: On or Off
	current_firmware=$(firmwarepasswd -check | awk '{print $NF}')

	# Was the correct amount of options supplied to the script
	if [ $options_given -ne $option_list ]
	then
		LogMe "$message"
		exit 1
	fi

	# Do not run if the current firmware status does not match the required status
	if [[ "$current_firmware" != "$password_status" ]]
	then
		LogMe "Firmware is currently ${current_firmware}.  Wrong context for $new_change_delete firmware password"
		exit 1
	fi

	$firmware_passwd_update	
}

function reboot_device {

	reboot_me="$1"
	current_boot=$(bless -getBoot)
	LogMe "Current boot device: $current_boot"
	system_boot=$(diskutil info / | awk '/Device Node:/ {print $NF}')
	LogMe "System boot: $system_boot"

	case $reboot_me in

		error)
			if [[ "$current_boot" != "$system_boot" ]]
			then
				DoAbort "System boot does not match current boot"
			else
				reboot_flag=true
			fi
			;;
		set) 
			if [[ "$current_boot" != "$system_boot" ]]
			then
				LogMe "System boot does not match current boot.  Set, firmware password but do not reboot"
			else
				reboot_flag=true
			fi
			;;
		ignore)
			if [[ "$current_boot" != "$system_boot" ]]
			then
				LogMe "System boot does not match current boot.  Set firmware password and reboot regardless"
			fi

			reboot_flag=true
			;;
		*)
			reboot_flag=false
			;;			
	esac
}


##################
# Script Start

reboot_flag=false

# Default - do not reboot at script end; consider using Fileset properties for reboot.
# Firmware password change requires reboot.  Tests for alternate boot drive selected
# Alternative options: error, set or ignore
# Use ignore to set default to reboot
# Uncomment command as desired
# error: Script will abort and no firmware password will be set if set boot drive does not match current booted drive
# reboot_device error
# set: Script will set the firmware password without a reboot attempt if set boot drive does not match current booted drive
# reboot_device set
# ignore: Script will continue regardless, setting firmware password and rebooting
# reboot_device ignore

# Set requirements based upon supplied options
case "$new_change_delete" in

	"new")
		message="Firmware Password Error: $new_change_delete expected format: new new_password"
		fwpassnew="$2"
		fwpassold=""
		password_status=No
		option_list=2
		check_password "new"
		set_password new_password
		;;
	"change")
		message="Firmware Password Error: $new_change_delete expected format: change new_password old_password"
		fwpassnew="$2"
		fwpassold="$3"
		password_status=Yes
		option_list=3
		check_password "old"
		check_password "new"
		set_password change_password
		;;
	"delete")
		message="Firmware Password Error: $new_change_delete expected format: delete old_password"
		fwpassnew=""
		fwpassold="$2"
		password_status=Yes
		option_list=2
		check_password "old"
		set_password delete_password
		;;
	*)
		DoAbort "Check details at the beginning of the script to confirm usage.  Exiting..."
		;;
esac

LogMe "Completed Firmware Password Change"


# reboot is required after setting firmware.  If reboot_flag has not been set to true in this script, consider setting Fileset properties
# Note, firmwarepasswd custom fields may not report correctly until a reboot has been completed
if [[ "$reboot_flag" == "true" ]]
then
	LogMe "Setting boot device to system and rebooting..."
	/usr/sbin/bless --device "$system_boot" --setBoot
	reboot
else
	LogMe "Reboot not set.  Firmware password setting requires a reboot"
fi

exit 0